package com.mgq.java8.collector;


import com.mgq.java8.stream.CaloricLevel;
import com.mgq.java8.stream.Dish;

import javax.swing.text.html.Option;
import java.util.*;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.*;
import static com.mgq.java8.stream.StreamTest.menu;

/**
 * <b>功能：</b>CollectorsTest<br>
 * <b>Copyright mgq</b>
 * <li>版本&nbsp;&nbsp;&nbsp;&nbsp;修改日期&nbsp;&nbsp;&nbsp;&nbsp;部　　门&nbsp;&nbsp;&nbsp;&nbsp;作　者&nbsp;&nbsp;&nbsp;&nbsp;变更内容</li><hr>
 * <li>v1.0&nbsp;&nbsp;&nbsp;&nbsp;20220327&nbsp;&nbsp;营业后台&nbsp;&nbsp;&nbsp;&nbsp;马广奇&nbsp;&nbsp;&nbsp;&nbsp;创建类</li>
 * <br><br>
 *
 * @author 马广奇
 * @version 2022-03-27 16:25
 */
public class CollectorsTest {
    public static void main(String[] args) {
//        testCollector();
//        testReducing();
//        testGroup();
        testPartition();
    }

    /**
     * 归约和汇总 maxBy(),minBy(),summingInt(),averagingInt(),summarizingInt(),summarizingDouble();
     * joining()
     */
    public static void testCollector() {
        //1.统计有多少个菜  count
        long count = menu.stream().count();
        //2.计算菜单中热量最高的食物 最大值:maxBy
        Optional<Dish> collect = menu.stream().collect(maxBy(Comparator.comparing(Dish::getCalories)));
        //等价
        Optional<Dish> collect2 = menu.stream().max(Comparator.comparing(Dish::getCalories));
        collect.ifPresent(System.out::println);
        collect2.ifPresent(System.out::println);
        System.out.println("---------------------------");
        //3.计算菜单中热量最小的食物 最小值:minBy
        Optional<Dish> min = menu.stream().min(Comparator.comparing(Dish::getCalories));
        min.ifPresent(System.out::println);
        System.out.println("---------------------------");

        //汇总  summing(求和) summarizing(总结)
        //Collectors提供了一个summingInt,它接受一个把对象映射为求和所需要的int的函数
        Integer total = menu.stream().collect(summingInt(Dish::getCalories));
        //求平均值
        Double avg = menu.stream().collect(averagingInt(Dish::getCalories));
        System.out.println(total);
        System.out.println(avg);
        IntSummaryStatistics summarizing = menu.stream().collect(summarizingInt(Dish::getCalories));
        //IntSummaryStatistics{count=9, sum=4200, min=120, average=466.666667, max=800}
        //IntSummaryStatistics包含了个数,最大值,最小值,平均值等
        System.out.println(summarizing);
        System.out.println("---------------------------");
        //joining连接字符串
        String collect1 = menu.stream().map(Dish::getName).collect(joining(","));
        System.out.println(collect1);
    }

    /**
     * 上面所有的方法 其实都是Collectors.reducing()工厂方法提供的更广义归约的特殊情况
     * reducing()函数需要3个参数
     * 第一个参数:归约操作的起始值,也是流中没有元素的返回值
     * 第二个参数:需要一个转换的函数
     * 第三个参数:BinaryOperator,将2项累积成一个值
     */
    public static void testReducing() {
        //2个是等价的
        int total = menu.stream().collect(Collectors.reducing(0, Dish::getCalories, ((integer, integer2) -> integer + integer2)));
        Integer collect = menu.stream().collect(summingInt(Dish::getCalories));
        System.out.println(total);
        System.out.println(collect);
        System.out.println("---------------------------");
        String collect1 = menu.stream().collect(reducing("", Dish::getName, (s1, s2) -> s1 + s2));
        System.out.println(collect1);
    }

    /**
     * 测试分组
     */
    public static void testGroup() {
        //1.根据类型分组
        Map<Dish.Type, List<Dish>> collect = menu.stream().collect(groupingBy(Dish::getType));
        System.out.println(collect);
        //2.自定义分组. 根据卡路里大小分组
        Map<CaloricLevel, List<Dish>> collect1 = menu.stream().collect(groupingBy(dish -> {
            if (dish.getCalories() <= 400) {
                return CaloricLevel.DIET;
            } else if (dish.getCalories() <= 700) {
                return CaloricLevel.NORMAL;
            } else {
                return CaloricLevel.FAT;
            }
        }));
        System.out.println(collect1);
        System.out.println("---------------------------");
        //3.多级分组. 先按照类型分, 再按照卡路里分
        //{OTHER={DIET=[Dish{name='rice', vegetarian=true, calories=350, type=OTHER}, Dish{name='season fruit', vegetarian=true, calories=120, type=OTHER}],
        //        NORMAL=[Dish{name='french fries', vegetarian=true, calories=530, type=OTHER}, Dish{name='pizza', vegetarian=true, calories=550, type=OTHER}]},
        // MEAT={DIET=[Dish{name='chicken', vegetarian=false, calories=400, type=MEAT}],
        //       NORMAL=[Dish{name='beef', vegetarian=false, calories=700, type=MEAT}],
        //       FAT=[Dish{name='pork', vegetarian=false, calories=800, type=MEAT}]},
        // FISH={DIET=[Dish{name='prawns', vegetarian=false, calories=300, type=FISH}],
        //       NORMAL=[Dish{name='salmon', vegetarian=false, calories=450, type=FISH}]}}
        Map<Dish.Type, Map<CaloricLevel, List<Dish>>> collect2 = menu.stream().collect(groupingBy(Dish::getType, groupingBy(dish -> {
            if (dish.getCalories() <= 400) {
                return CaloricLevel.DIET;
            } else if (dish.getCalories() <= 700) {
                return CaloricLevel.NORMAL;
            } else {
                return CaloricLevel.FAT;
            }
        })));
        System.out.println(collect2);
        System.out.println("---------------------------");
        //group 完count
        Map<Dish.Type, Long> collect3 = menu.stream().collect(groupingBy(Dish::getType, counting()));
        System.out.println(collect3);
        System.out.println("---------------------------");
        //group和maxBy 先根据类型group然后再找卡路里最大的
        Map<Dish.Type, Optional<Dish>> collect4 = menu.stream()
                .collect(groupingBy(Dish::getType, maxBy(Comparator.comparingInt(Dish::getCalories))));

        //使用group和collectingAndThen()方法将上面的Option去掉
        Map<Dish.Type, Dish> collect5 = menu.stream()
                .collect(groupingBy(Dish::getType, collectingAndThen(maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get)));
        System.out.println(collect4);
        System.out.println("---------------------------");

        //分组求和 group和summingInt 分组求和
        Map<Dish.Type, Integer> collect6 = menu.stream().collect(groupingBy(Dish::getType, summingInt(Dish::getCalories)));
        System.out.println(collect6);
        System.out.println("---------------------------");

        //group和mapping  分组然后收集 此处是根据类型分组,然后每种类型有哪些CaloricLevel
        Map<Dish.Type, Set<CaloricLevel>> collect7 = menu.stream().collect(groupingBy(Dish::getType, mapping(dish -> {
            if (dish.getCalories() <= 400) {
                return CaloricLevel.DIET;
            } else if (dish.getCalories() <= 700) {
                return CaloricLevel.NORMAL;
            } else {
                return CaloricLevel.FAT;
            }
        }, toSet())));
        //和上面相同,不过是指定了使用HashSet
        Map<Dish.Type, Set<CaloricLevel>> collect8 = menu.stream().collect(groupingBy(Dish::getType, mapping(dish -> {
            if (dish.getCalories() <= 400) {
                return CaloricLevel.DIET;
            } else if (dish.getCalories() <= 700) {
                return CaloricLevel.NORMAL;
            } else {
                return CaloricLevel.FAT;
            }
        }, toCollection(HashSet::new))));
        System.out.println(collect7);
        System.out.println(collect8);
    }

    /**
     * 测试分区
     * 分区和分组的区别是,分区函数的返回值是一个布尔值,这意味着返回的map key是Boolean类型,于是最多2组,true或者false
     */
    public static void testPartition() {
        System.out.println("---------------------------");
        //1.根据是不是素食来返回 true是素食集合,false不是素食集合
        Map<Boolean, List<Dish>> collect = menu.stream().collect(partitioningBy(Dish::isVegetarian));
        System.out.println(collect);

        System.out.println("---------------------------");
        //2.partitioningBy()和groupingBy()结合使用 现根据素食分区,然后根据类型分组, 最后得到的就是 素食有那几种,不是素食的有那几种
        Map<Boolean, Map<Dish.Type, List<Dish>>> collect1 = menu.stream().collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));
        System.out.println(collect1);

        //3.找到素食和非素食中热量最高的菜
        Map<Boolean, Dish> collect2 = menu.stream().collect(partitioningBy(Dish::isVegetarian, collectingAndThen(
                maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get
        )));
        Map<Boolean, Dish> collect5 = menu.stream()
                .collect(groupingBy(Dish::isVegetarian, collectingAndThen(maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get)));
        System.out.println("---------------------------");
        System.out.println(collect5);

        System.out.println(collect2);

    }
}
